package com.xr.miniprogram.service.impl;



import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;

import com.xr.common.config.WXPayConfig;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.xr.common.constant.ConstantUtils;
import com.xr.common.constant.OrderStatusConstants;
import com.xr.common.enums.RedisDelayQueueEnum;
import com.xr.common.utils.uuid.UUID;

import com.xr.miniprogram.domain.MiniProgramOrder;
import com.xr.miniprogram.domain.MiniProgramRefund;
import com.xr.miniprogram.domain.MiniProgramUser;
import com.xr.miniprogram.domain.vo.WxPayRespVO;
import com.xr.miniprogram.service.*;
import com.xr.miniprogram.tools.RedissonUtil.RedisDelayQueueUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.stream.Stream;


@Service
@Slf4j
public class WXPayServiceImpl implements WXPayService {
    @Autowired
    WXPayConfig wxPayConfig;

    @Resource
    private CloseableHttpClient httpClient;

    @Resource
    IMiniProgramUserService userService;
    @Resource
    IMiniProgramOrderService orderService;
    @Resource
    IMiniProgramOrderUserService orderUserService;
    @Resource
    IMiniProgramRefundService refundService;
    @Autowired
    private RedisDelayQueueUtil redisDelayQueueUtil;

    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public WxPayRespVO createOrder(Long orderId) throws Exception{
        MiniProgramOrder miniProgramOrder = orderService.getById(orderId);
        Long userId = miniProgramOrder.getMiniProgramUserId();
        MiniProgramUser miniProgramUser = userService.getOne(new QueryWrapper<MiniProgramUser>().eq("mini_program_user_id", userId));
        String openId = miniProgramUser.getOpenId();
        BigDecimal orderPrice = miniProgramOrder.getOrderPrice();
        String standardsName = miniProgramOrder.getStandardsName();
        String orderNumber = miniProgramOrder.getOrderNumber();
        Payer payer = new Payer();
        payer.setOpenid(openId);
        //请求URL
        HttpPost httpPost = new HttpPost(ConstantUtils.JSAPI_URL);

        // 请求body参数
        Gson gson = new Gson();
        HashMap<Object, Object> map = new HashMap<>();
        map.put("appid", wxPayConfig.getAppId());
        map.put("mchid", wxPayConfig.getMchId());
        map.put("description", standardsName);
        map.put("out_trade_no", orderNumber);
        map.put("notify_url", wxPayConfig.getNotifyDomain()+ ConstantUtils.NOTIFY_URL);
        Map<String, Object> amount = new HashMap<>();
        amount.put("total",orderPrice.multiply(new BigDecimal("100")).setScale(0, BigDecimal.ROUND_HALF_UP).intValue());
        amount.put("currency", "CNY");
        map.put("amount", amount);
        map.put("payer", payer);
        String jsonParams = gson.toJson(map);
        log.info("请求参数:" + jsonParams);
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpPost);
        WxPayRespVO vo = new WxPayRespVO();

        try {
            String result = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功,返回结果 = " + result);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                System.out.println("预下单失败,响应状态码 = " + statusCode + ",返回结果 = " + result);
                throw new IOException("request failed");
            }

            Map<String, Object> resultMap = gson.fromJson(result, HashMap.class);
            String prepay_id = (String) resultMap.get("prepay_id");
            Long timeStamp = System.currentTimeMillis() / 1000;
            vo.setTimeStamp(String.valueOf(timeStamp));
            String substring = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
            vo.setNonceStr(substring);
            String signatureStr = Stream.of(wxPayConfig.getAppId(), String.valueOf(timeStamp), substring, "prepay_id=" + prepay_id)
                    .collect(Collectors.joining("\n", "", "\n"));
            String sign = wxPayConfig.getSign(signatureStr);
            vo.setPaySign(sign);
            vo.setPrepayId("prepay_id=" + prepay_id);
            return vo;
        } finally {
            response.close();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processOrder(Map<String, Object> bodyMap) throws Exception {
        log.info("处理订单");
        //解密报文
        String plainText = decryResource(bodyMap);
        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        String orderNo = (String) plainTextMap.get("out_trade_no");
        //关闭延迟队列
        redisDelayQueueUtil.removeDelayedQueue(orderNo, RedisDelayQueueEnum.orderPaymentTimeout.getCode());
        /**
         * 在对业务数据进行状态检查和处理之前，要采用数据锁进行并发控制，以避免函数重入造成的数据混乱。
         */
        if (lock.tryLock()) {
            try {
                //处理重复通知
                Long orderStatus = orderService.selectMiniProgramOrderByOrderNumber(orderNo).getOrderStatusId();
                if (!OrderStatusConstants.SERVICE_ORDER.equals(orderStatus)) {
                    return;
                }
                orderService.updateStatusByOrderNo(orderNo, OrderStatusConstants.SERVICE_ORDER);
            } finally {
                //主动释放锁
                lock.unlock();
            }
        }
    }

    @Override
    public String queryOrder(String orderNo) throws Exception {
        log.info("查单接口调用===>{}",orderNo);
        String url = ConstantUtils.QUERY_PAY_RESULT_URL;
        String queryUrl = String.format(url, orderNo).concat("?mchid=").concat(wxPayConfig.getMchId());

        HttpGet httpGet = new HttpGet(queryUrl);
        httpGet.setHeader("Accept", "application/json");
        //完成签名并执行请求
        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
            String result = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功,返回结果 = " + result);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                System.out.println("预下单失败,响应状态码 = " + statusCode + ",返回结果 = " + result);
                throw new IOException("request failed");
            }
            return result;
        }

    }

    @Override
    public String queryRefund(String refundNo) throws Exception {
        log.info("查询退款接口 ===>{}",refundNo);
        String url = ConstantUtils.SELECT_REFUND_URL;
        String refundUrl = String.format(url, refundNo);
        HttpGet httpGet = new HttpGet(refundUrl);
        httpGet.setHeader("Accept", "application/json");
        //完成签名并执行请求
        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
            String result = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功,查询的退款返回结果 = " + result);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                System.out.println("查询退款失败,响应状态码 = " + statusCode + ",返回结果 = " + result);
                throw new IOException("request failed");
            }
            return result;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processRefundOrder(Map<String, Object> bodyMap) throws Exception {
        log.info("退款单");
        //解密报文
        String plainText = decryResource(bodyMap);
        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        String orderNo = (String) plainTextMap.get("out_trade_no");
        if (lock.tryLock()) {
            try {
                //处理重复通知
                Long orderStatus = orderService.selectMiniProgramOrderByOrderNumber(orderNo).getOrderStatusId();
                if (!OrderStatusConstants.RETURN_ORDER.equals(orderStatus)) {
                    return;
                }
                orderService.updateStatusByOrderNo(orderNo, OrderStatusConstants.RETURNED_ORDER);
            } finally {
                //主动释放锁
                lock.unlock();
            }
        }

    }

    /**
     * 根据订单号查询微信支付查单接口
     * @param orderNo
     */
    @Override
    public void checkOrderStatus(String orderNo) throws Exception {
        log.info("根据订单号核实订单状态===>{}",orderNo);
        MiniProgramOrder miniProgramOrder = orderService.getOne(new QueryWrapper<MiniProgramOrder>().eq("order_number", orderNo));
        Long orderStatusId = miniProgramOrder.getOrderStatusId();
        if (orderStatusId == 4L){
            log.info("核实订单{}支付成功",orderNo);
            orderService.updateStatusByOrderNo(orderNo,OrderStatusConstants.SERVICE_ORDER);
        }else {
            log.info("核实订单{}未支付",orderNo);
            //如果订单未支付，调用关单接口
            this.closeOrder(orderNo);
            //更新本地状态
            orderService.updateStatusByOrderNo(orderNo,OrderStatusConstants.TIMEOUT_ORDER);
        }
    }

    private String decryResource(Map<String, Object> bodyMap) throws Exception {
        log.info("密文解密");
        Map<String,String> resource = (Map)bodyMap.get("resource");
        String ciphertext = resource.get("ciphertext");
        String nonce = resource.get("nonce");
        String associated_data = resource.get("associated_data");
        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String plainText = aesUtil.decryptToString(associated_data.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
        log.info("明文：{}",plainText);
        return plainText;

    }
    /**
     * 关闭订单
     *
     * @param orderNo
     * @return
     */
    @Override
    public void closePayOrder(String orderNo) throws Exception {

        //调用微信支付的关单接口
        this.closeOrder(orderNo);
        //消费延迟队列
        redisDelayQueueUtil.removeDelayedQueue(orderNo, RedisDelayQueueEnum.orderPaymentTimeout.getCode());
        //更新订单状态
        orderService.updateStatusByOrderNo(orderNo, OrderStatusConstants.CANCEL_ORDER);
    }

    /**
     * 关单接口的调用
     * @param orderNo
     * @throws Exception
     */
        private void closeOrder(String orderNo) throws Exception {
        log.info("关闭订单号：{}",orderNo);
        String url = ConstantUtils.CLOSE_PAY_ORDER_URL;
        String closeUrl = String.format(url, orderNo);
        HttpPost httpPost = new HttpPost(closeUrl);

        Gson gson = new Gson();
        Map<String, String> paramsMap = new HashMap<>();
        paramsMap.put("mchid",wxPayConfig.getMchId());
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数---->{}",jsonParams);

        //将请求参数设置到请求对象中
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpPost);

        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功");
            } else if (statusCode == 204) {
                log.info("成功,状态码204");
            } else {
                System.out.println("关单失败,响应状态码 = " + statusCode);
                throw new IOException("request failed");
                }
            }finally {
            response.close();
        }
    }

    @Transactional
    @Override
    public void refundOrder(String orderNo) throws Exception {
        //根据订单编号查找退款单
        MiniProgramRefund miniProgramRefund = refundService.getRefundByOrderNumber(orderNo);

        //调用退款api
        String orderRefundUrl = ConstantUtils.ORDER_REFUND_URL;
        HttpPost httpPost = new HttpPost(orderRefundUrl);

        //请求body参数
        Gson gson = new Gson();
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("out_trade_no",orderNo);
        paramsMap.put("out_refund_no",miniProgramRefund.getOutRefundNo());
        paramsMap.put("reason",miniProgramRefund.getReason());
        paramsMap.put("notify_url",wxPayConfig.getNotifyDomain()+ConstantUtils.REFUND_NOTIFY);
        Map<String, Object> amountMap = new HashMap<>();
        amountMap.put("refund",miniProgramRefund.getRefundMoney().multiply(new BigDecimal("100")).setScale(0, BigDecimal.ROUND_HALF_UP).intValue());
        amountMap.put("total",miniProgramRefund.getTotalMoney().multiply(new BigDecimal("100")).setScale(0, BigDecimal.ROUND_HALF_UP).intValue());
        amountMap.put("currency","CNY");
        paramsMap.put("amount",amountMap);
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数---->{}",jsonParams);

        //将请求参数设置到请求对象中
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            String result = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功,退款返回结果 = " + result);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                System.out.println("退款异常,响应状态码 = " + statusCode + ",返回结果 = " + result);
                throw new IOException("request failed");
            }
            orderService.updateStatusByOrderNo(orderNo,OrderStatusConstants.RETURN_ORDER);

            refundService.updateRefund(result);
        } finally {
            response.close();
        }
    }
}
